/*
 * GPIO C++ unit tests
 *
 * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0
 *
 * This test code is in the Public Domain (or CC0 licensed, at your option.)
 *
 * Unless required by applicable law or agreed to in writing, this
 * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.
 */

#define CATCH_CONFIG_MAIN

#include <stdio.h>
#include "esp_err.h"
#include "freertos/portmacro.h"
#include "gpio_cxx.hpp"
#include "test_fixtures.hpp"

#include "catch.hpp"

extern "C" {
#include "Mockgpio.h"
}

// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
    return "test";
}

using namespace std;
using namespace idf;

TEST_CASE("gpio num out of range")
{
    CHECK_THROWS_AS(GPIONum(-1), GPIOException&);
    CHECK_THROWS_AS(GPIONum(static_cast<uint32_t>(GPIO_NUM_MAX)), GPIOException&);
    CHECK_THROWS_AS(GPIONum(24), GPIOException&); // On ESP32, 24 isn't a valid GPIO number
}

TEST_CASE("gpio num operator")
{
    GPIONum gpio_num_0(18u);
    GPIONum gpio_num_1(18u);
    GPIONum gpio_num_2(19u);

    CHECK(gpio_num_0 == gpio_num_1);
    CHECK(gpio_num_2 != gpio_num_1);
}

TEST_CASE("drive strength out of range")
{
    CHECK_THROWS_AS(GPIODriveStrength(-1), GPIOException&);
    CHECK_THROWS_AS(GPIODriveStrength(static_cast<uint32_t>(GPIO_DRIVE_CAP_MAX)), GPIOException&);
}

TEST_CASE("drive strength as expected")
{
    CHECK(GPIODriveStrength::DEFAULT().get_value() == GPIO_DRIVE_CAP_2);
    CHECK(GPIODriveStrength::WEAK().get_value() == GPIO_DRIVE_CAP_0);
    CHECK(GPIODriveStrength::LESS_WEAK().get_value() == GPIO_DRIVE_CAP_1);
    CHECK(GPIODriveStrength::MEDIUM().get_value() == GPIO_DRIVE_CAP_2);
    CHECK(GPIODriveStrength::STRONGEST().get_value() == GPIO_DRIVE_CAP_3);
}

TEST_CASE("pull mode create functions work as expected")
{
    CHECK(GPIOPullMode::FLOATING().get_value() == 3);
    CHECK(GPIOPullMode::PULLUP().get_value() == 0);
    CHECK(GPIOPullMode::PULLDOWN().get_value() == 1);
}

TEST_CASE("GPIOIntrType create functions work as expected")
{
    CHECK(GPIOWakeupIntrType::LOW_LEVEL().get_value() == GPIO_INTR_LOW_LEVEL);
    CHECK(GPIOWakeupIntrType::HIGH_LEVEL().get_value() == GPIO_INTR_HIGH_LEVEL);
}

TEST_CASE("output resetting pin fails")
{
    CMOCK_SETUP();
    gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_FAIL);

    CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);

    Mockgpio_Verify();
}

TEST_CASE("output setting direction fails")
{
    CMOCK_SETUP();
    gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
    gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);

    CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);

    Mockgpio_Verify();
}

TEST_CASE("output constructor sets correct arguments")
{
    CMOCK_SETUP();
    gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), ESP_OK);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), GPIO_MODE_OUTPUT, ESP_OK);

    GPIO_Output gpio(VALID_GPIO);

    Mockgpio_Verify();
}

TEST_CASE("output set high fails")
{
    GPIOFixture fix;
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_FAIL);

    GPIO_Output gpio(fix.num);

    CHECK_THROWS_AS(gpio.set_high(), GPIOException&);
}

TEST_CASE("output set high success")
{
    GPIOFixture fix;
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_OK);

    GPIO_Output gpio(fix.num);

    gpio.set_high();
}

TEST_CASE("output set low fails")
{
    GPIOFixture fix;
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_FAIL);

    GPIO_Output gpio(fix.num);

    CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
}

TEST_CASE("output set low success")
{
    GPIOFixture fix;
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_OK);

    GPIO_Output gpio(fix.num);

    gpio.set_low();
}

TEST_CASE("output set drive strength")
{
    GPIOFixture fix(VALID_GPIO);
    gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_DRIVE_CAP_0, ESP_OK);

    GPIO_Output gpio(fix.num);

    gpio.set_drive_strength(GPIODriveStrength::WEAK());
}

TEST_CASE("output get drive strength")
{
    GPIOFixture fix(VALID_GPIO);
    gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
    gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
    gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);

    GPIO_Output gpio(fix.num);

    CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
}

TEST_CASE("GPIOInput setting direction fails")
{
    CMOCK_SETUP();
    gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
    gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);

    CHECK_THROWS_AS(GPIOInput gpio(VALID_GPIO), GPIOException&);

    Mockgpio_Verify();
}

TEST_CASE("constructor sets correct arguments")
{
    CMOCK_SETUP();
    gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), ESP_OK);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), GPIO_MODE_INPUT, ESP_OK);

    GPIOInput gpio(VALID_GPIO);

    Mockgpio_Verify();
}

TEST_CASE("get level low")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0);

    GPIOInput gpio(fix.num);

    CHECK(gpio.get_level() == GPIOLevel::LOW);
}

TEST_CASE("get level high")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1);

    GPIOInput gpio(fix.num);

    CHECK(gpio.get_level() == GPIOLevel::HIGH);
}

TEST_CASE("set pull mode fails")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_FLOATING, ESP_FAIL);

    GPIOInput gpio(fix.num);

    CHECK_THROWS_AS(gpio.set_pull_mode(GPIOPullMode::FLOATING()), GPIOException&);
}

TEST_CASE("GPIOInput set pull mode floating")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_FLOATING, ESP_OK);

    GPIOInput gpio(fix.num);

    gpio.set_pull_mode(GPIOPullMode::FLOATING());
}

TEST_CASE("GPIOInput set pull mode pullup")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_PULLUP_ONLY, ESP_OK);

    GPIOInput gpio(fix.num);

    gpio.set_pull_mode(GPIOPullMode::PULLUP());
}

TEST_CASE("GPIOInput set pull mode pulldown")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_PULLDOWN_ONLY, ESP_OK);

    GPIOInput gpio(fix.num);

    gpio.set_pull_mode(GPIOPullMode::PULLDOWN());
}

TEST_CASE("GPIOInput wake up enable fails")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_INTR_LOW_LEVEL, ESP_FAIL);

    GPIOInput gpio(fix.num);

    CHECK_THROWS_AS(gpio.wakeup_enable(GPIOWakeupIntrType::LOW_LEVEL()), GPIOException&);
}

TEST_CASE("GPIOInput wake up enable high int")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_INTR_HIGH_LEVEL, ESP_OK);

    GPIOInput gpio(fix.num);

    gpio.wakeup_enable(GPIOWakeupIntrType::HIGH_LEVEL());
}

TEST_CASE("GPIOInput wake up disable fails")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), ESP_FAIL);

    GPIOInput gpio(fix.num);

    CHECK_THROWS_AS(gpio.wakeup_disable(), GPIOException&);
}

TEST_CASE("GPIOInput wake up disable high int")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), ESP_OK);

    GPIOInput gpio(fix.num);

    gpio.wakeup_disable();
}

TEST_CASE("GPIO_OpenDrain setting direction fails")
{
    CMOCK_SETUP();
    gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
    gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);

    CHECK_THROWS_AS(GPIO_OpenDrain gpio(VALID_GPIO), GPIOException&);

    Mockgpio_Verify();
}

TEST_CASE("GPIO_OpenDrain constructor sets correct arguments")
{
    CMOCK_SETUP();
    gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), ESP_OK);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT,
            ESP_OK);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT_OUTPUT_OD,
            ESP_OK);

    GPIO_OpenDrain gpio(VALID_GPIO);

    Mockgpio_Verify();
}

TEST_CASE("GPIO_OpenDrain set floating fails")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT_OUTPUT_OD,
            ESP_OK);
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_FAIL);

    GPIO_OpenDrain gpio(fix.num);

    CHECK_THROWS_AS(gpio.set_floating(), GPIOException&);
}

TEST_CASE("GPIO_OpenDrain set floating success")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT_OUTPUT_OD,
            ESP_OK);
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_OK);

    GPIO_OpenDrain gpio(fix.num);

    gpio.set_floating();
}

TEST_CASE("GPIO_OpenDrain set low fails")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT_OUTPUT_OD,
            ESP_OK);
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_FAIL);

    GPIO_OpenDrain gpio(fix.num);

    CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
}

TEST_CASE("GPIO_OpenDrain set low success")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT_OUTPUT_OD,
            ESP_OK);
    gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_OK);

    GPIO_OpenDrain gpio(fix.num);

    gpio.set_low();
}

TEST_CASE("GPIO_OpenDrain set drive strength")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT_OUTPUT_OD,
            ESP_OK);

    gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_DRIVE_CAP_0, ESP_OK);
    GPIO_OpenDrain gpio(fix.num);

    gpio.set_drive_strength(GPIODriveStrength::WEAK());
}

TEST_CASE("GPIO_OpenDrain get drive strength")
{
    GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
    gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
            GPIO_MODE_INPUT_OUTPUT_OD,
            ESP_OK);
    gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
    gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
    gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);

    GPIO_OpenDrain gpio(fix.num);

    CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
}
